""" S3D.ElectricCraft.Renderer package """
from PyQt4.QtGui import *
from PyQt4.QtCore import Qt, QObject, QRectF, QPointF, SIGNAL
from S3D import range_2
from S3D.ElectricCraft import ViewModel as game

GRID_CELL_SIZE              = 25
GRID_CELL_PEN               = QPen(Qt.DotLine)
GRID_CELL_HIGHLIGHT_PEN     = QPen(Qt.SolidLine)

INACTIVE_WIRE_COLOR         = QColor("DarkBlue")
ACTIVE_WIRE_COLOR           = QColor("Red")
WIRE_WIDTH                  = 4

BRIDGE_CROSS_OFFSET         = 0.2

POWER_SUPPLY_COLOR          = ACTIVE_WIRE_COLOR
POWER_SUPPLY_SIZE           = GRID_CELL_SIZE / 2

DIODE_COLOR                 = QColor("Black")
DIODE_PEN_WIDTH             = 3

KEY_COLOR                   = QColor("Black")

INPUT_COLOR                 = QColor("Green")
OUTPUT_COLOR                = QColor("Brown")

CHIP_COLOR                  = QColor(70, 70, 70)
CHIP_BODY_WIDTH             = GRID_CELL_SIZE * 0.15
CHIP_FONT                   = QFont("Courier New", 11)
CHIP_PREVIEW_WIDTH          = GRID_CELL_SIZE / 4
CHIP_PREVIEW_HEIGHT         = GRID_CELL_SIZE * 0.7
CHIP_PREVIEW_JUMPER_WIDTH   = GRID_CELL_SIZE * 2 / 3

MARK_COLOR                  = QColor("Black")
MARK_OFFSET                 = GRID_CELL_SIZE * 0.15 + 3

BORDER_COLOR                = QColor("Black")
BORDER_WIDTH                = 4
BORDER_BACK_COLOR           = QColor("White")

def _GridToScene(x, y):
    resX = x * GRID_CELL_SIZE + GRID_CELL_SIZE / 2
    resY = y * GRID_CELL_SIZE + GRID_CELL_SIZE / 2
    return resX, resY

class RendGrid(QGraphicsItem):
    def __init__(self, model):
        super(RendGrid, self).__init__()

        self.model = model
        self.size  = (model.sizeX, model.sizeY)
        self.title = ""

    def paint(self, painter, options, widget = None):
        sizeX, sizeY = self.size

        # render grid cells
        if not self.model.isPreview:
            painter.setPen(GRID_CELL_PEN)
            for k in range(sizeX + 1):
                painter.drawLine(0, k * GRID_CELL_SIZE, sizeX * GRID_CELL_SIZE, k * GRID_CELL_SIZE)

            for k in range(sizeY + 1):
                painter.drawLine(k * GRID_CELL_SIZE, 0, k * GRID_CELL_SIZE, sizeY * GRID_CELL_SIZE)

        # render title and command buttons
        self.paintTitle(painter)

        # render items
        board = self.model.items

        for x, y in range_2(sizeX, sizeY):
            item = board[x][y]
            if item is None:
                continue

            if item.type == game.VIEW_ITEM_WIRE:
                self.paintWire(painter, x, y, item.group)
            elif item.type == game.VIEW_ITEM_BRIDGE:
                self.paintBridge(painter, x, y, item.group)
            elif item.type == game.VIEW_ITEM_KEY:
                self.paintKey(painter, x, y, item.direction, item.state)
            elif item.type == game.VIEW_ITEM_INPUT:
                self.paintInput(painter, x, y)
            elif item.type == game.VIEW_ITEM_OUTPUT:
                self.paintOutput(painter, x, y)
            elif item.type == game.VIEW_ITEM_POWER:
                self.paintPower(painter, x, y)
            elif item.type == game.VIEW_ITEM_DIODE:
                self.paintDiode(painter, x, y, item.direction)
            elif item.type == game.VIEW_ITEM_CHIP:
                self.paintChip(painter, x, y)
            elif item.type == game.VIEW_ITEM_CHIP_INPUT or item.type == game.VIEW_ITEM_CHIP_OUTPUT:
                self.paintChipJumper(painter, x, y, item)
            elif item.type == game.VIEW_ITEM_MARK:
                self.paintMark(painter, x, y, item)
            else:
                print("Unknown item type: %s" % game.VIEW_ITEM_NAMES[item.type])

    def isConnectable(self, x, y, dirIndex):
        board = self.model.items
        cur   = board[x][y]
        if cur.constr[dirIndex]:
            return False

        if self.model.isPreview:
            return True

        sizeX, sizeY = self.size
        dx, dy = game.DIR_LIST[dirIndex]
        x2 = x + dx
        y2 = y + dy
        if x2 < 0 or x2 >= sizeX or y2 < 0 or y2 >= sizeY:
            return False

        next = board[x2][y2]
        if next is None:
            return False

        revDirIndex = game.getRevertedDir(dirIndex)
        if next.constr[revDirIndex]:
            return False

        return True

    def paintTitle(self, p):
        p.setFont(CHIP_FONT)
        p.drawText(QRectF(0, -GRID_CELL_SIZE, GRID_CELL_SIZE * self.size[0], GRID_CELL_SIZE), self.title)

    def getWireColor(self, group):
        if group is not None and self.model.groups[group].state:
            return ACTIVE_WIRE_COLOR

        return INACTIVE_WIRE_COLOR

    def paintWire(self, painter, x, y, group):
        x0, y0 = _GridToScene(x, y)

        color = self.getWireColor(group)
        painter.setPen(QPen(color, WIRE_WIDTH))

        shown = False
        for index in range(game.DIRS_COUNT):
            if not self.isConnectable(x, y, index):
                continue

            dx, dy = game.DIR_LIST[index]
            x1, y1 = _GridToScene(x + dx / 2, y + dy / 2)
            x1 -= WIRE_WIDTH / 2 * dx
            y1 -= WIRE_WIDTH / 2 * dy
            painter.drawLine(x0, y0, x1, y1)
            shown = True

        if not shown:
            painter.drawLine(x0, y0, x0, y0)

#        if group:
#            x0, y0 = _GridToScene(x - 0.5, y - 0.5)
#            painter.setPen(QPen(QColor("Red")))
#            painter.drawText(QRectF(x0, y0, GRID_CELL_SIZE, GRID_CELL_SIZE), str(group))

    def paintBridge(self, p, x, y, group):
        # draw direct line
        directColor = self.getWireColor(group[0])
        p.setPen(QPen(directColor, WIRE_WIDTH))

        x0, y0 = _GridToScene(x - 0.5, y)
        x0 += WIRE_WIDTH / 2

        x1, y1 = _GridToScene(x + 0.5, y)
        x1 -= WIRE_WIDTH / 2
        p.drawLine(x0, y0, x1, y1)

        # draw cross
        crossColor = self.getWireColor(group[1])
        p.setPen(QPen(crossColor, WIRE_WIDTH))

        x0, y0 = _GridToScene(x, y - 0.25)
        y0 -= BRIDGE_CROSS_OFFSET
        x1, y1 = _GridToScene(x, y - 0.5)
        y1 += WIRE_WIDTH / 2
        p.drawLine(x0, y0, x1, y1)

        x0, y0 = _GridToScene(x, y + 0.25)
        y0 += BRIDGE_CROSS_OFFSET
        x1, y1 = _GridToScene(x, y + 0.5)
        y1 -= WIRE_WIDTH / 2
        p.drawLine(x0, y0, x1, y1)

#        x0, y0 = _GridToScene(x - 0.5, y - 0.5)
#        p.setPen(QPen(QColor("Red")))
#        p.drawText(QRectF(x0, y0, GRID_CELL_SIZE * 2, GRID_CELL_SIZE), "%d/%d" % (group[0] or -1, group[1] or -1))

    def paintPower(self, p, x, y):
        p.setPen(QPen(POWER_SUPPLY_COLOR))
        p.setBrush(QBrush(POWER_SUPPLY_COLOR))

        x0, y0 = _GridToScene(x, y)
        p.drawEllipse(x0 - POWER_SUPPLY_SIZE / 2, y0 - POWER_SUPPLY_SIZE / 2, POWER_SUPPLY_SIZE, POWER_SUPPLY_SIZE)

    def paintDiode(self, p, x, y, direction):
        dx0, dy0 = game.DIR_LIST[direction]
        dx, dy = (dx0 == 0) and (1, 0) or (0, 1) # orthogonal delta

        x1, y1 = _GridToScene(x - dx0 / 4 - dx / 4, y - dy0 / 4 - dy / 4)
        x2, y2 = _GridToScene(x - dx0 / 4 + dx / 4, y - dy0 / 4 + dy / 4)
        x3, y3 = _GridToScene(x + dx0 / 4, y + dy0 / 4)

        brush = QBrush(DIODE_COLOR)
        p.setBrush(brush)
        p.setPen(QPen(DIODE_COLOR))

        p.drawConvexPolygon(QPolygonF([
            QPointF(x1, y1),
            QPointF(x2, y2),
            QPointF(x3, y3)
        ]))

        p.setPen(QPen(brush, DIODE_PEN_WIDTH))

        x1, y1 = _GridToScene(x + dx0 / 4 - dx / 4, y + dy0 / 4 - dy / 4)
        x2, y2 = _GridToScene(x + dx0 / 4 + dx / 4, y + dy0 / 4 + dy / 4)
        p.drawLine(x1, y1, x2, y2)

    def paintInput(self, p, x, y):
        dx0, dy0 = 1, 0
        dx, dy = (dx0 == 0) and (1, 0) or (0, 1) # orthogonal delta

        x1, y1 = _GridToScene(x - dx0 / 4 - dx / 4, y - dy0 / 4 - dy / 4)
        x2, y2 = _GridToScene(x - dx0 / 4 + dx / 4, y - dy0 / 4 + dy / 4)
        x3, y3 = _GridToScene(x + dx0 / 4, y + dy0 / 4)

        p.setBrush(QBrush(INPUT_COLOR))
        p.setPen(QPen(INPUT_COLOR))

        p.drawConvexPolygon(QPolygonF([
            QPointF(x1, y1),
            QPointF(x2, y2),
            QPointF(x3, y3)
        ]))

    def paintOutput(self, p, x, y):
        p.setPen(QPen(OUTPUT_COLOR))
        p.setBrush(QBrush(OUTPUT_COLOR))

        x1, y1 = _GridToScene(x - 0.25, y - 0.25)
        p.drawRect(x1, y1, GRID_CELL_SIZE / 2, GRID_CELL_SIZE / 2)

    def paintKey(self, p, x, y, direction, state):
        dx, dy = (direction == 0) and (1, 0) or (0, 1)

        p.setPen(QPen(KEY_COLOR, WIRE_WIDTH))
        x1, y1 = _GridToScene(x - dx * 0.27, y - dy * 0.27)
        p.drawLine(x1, y1, x1, y1)

        x2, y2 = _GridToScene(x + dx * 0.27, y + dy * 0.27)
        x2 += 2 * dx # accuracy fix
        y2 += 2 * dy
        p.drawLine(x2, y2, x2, y2)

        item = self.model.items[x][y]
        if item.group is not None:
            if self.model.groups[item.group].switch:
                state = not state

        if state:
            x1, y1 = _GridToScene(x - dx * 0.03, y - dy * 0.03)
            x2, y2 = _GridToScene(x + dx * 0.03, y + dy * 0.03)
            x2 += 2 * dx # accuracy fix
            y2 += 2 * dy

            p.setPen(QPen(KEY_COLOR, WIRE_WIDTH))
            p.drawLine(x1, y1, x2, y2)

    def paintChip(self, p, x, y):
        p.setPen(QPen(CHIP_COLOR))
        p.setBrush(QBrush(CHIP_COLOR))

        x0, y0 = _GridToScene(x, y)
        x1, y1 = x0 - CHIP_PREVIEW_WIDTH / 2, y0 - CHIP_PREVIEW_HEIGHT / 2
        p.drawRect(x1, y1, CHIP_PREVIEW_WIDTH, CHIP_PREVIEW_HEIGHT)

        x0, y0 = _GridToScene(x - 0.5, y - 0.5)
        x1 = x0 + CHIP_PREVIEW_JUMPER_WIDTH / 4
        step = GRID_CELL_SIZE / 4

        for index in range(1, 4):
            y1 = y0 + step * index
            p.drawLine(x1, y1, x1 + CHIP_PREVIEW_JUMPER_WIDTH, y1)

    def paintChipJumper(self, p, x, y, item):
        x0, y0 = _GridToScene(x - 0.5, y - 0.5)
        x1, y1 = _GridToScene(x + 0.5, y + 0.5)

        isInput = (item.type == game.VIEW_ITEM_CHIP_INPUT)

        p.setPen(QPen(CHIP_COLOR, 2))
        p.setBrush(QBrush(CHIP_COLOR))

        if item.index == 0:
            p.drawLine(x0, y0, x1, y0) # top
        if isInput:
            p.drawLine(x0, y0, x0, y1) # left
        else:
            p.drawRect(x0, y0, CHIP_BODY_WIDTH, GRID_CELL_SIZE)

        if not isInput:
            p.drawLine(x1, y0, x1, y1) # right
        else:
            p.drawRect(x1 - CHIP_BODY_WIDTH, y0, CHIP_BODY_WIDTH, GRID_CELL_SIZE)

        if item.index + 1 == item.chip.jumpers:
            p.drawLine(x0, y1, x1, y1) # bottom
        else:
            p.setPen(QPen(CHIP_COLOR))
            p.drawLine(x0, y1, x1, y1) # bottom

        p.setFont(CHIP_FONT)

        if isInput:
            p.drawText(QRectF(x0, y0, GRID_CELL_SIZE - CHIP_BODY_WIDTH, GRID_CELL_SIZE),
                Qt.AlignCenter, str(item.index + 1))
        else:
            p.drawText(QRectF(x0 + CHIP_BODY_WIDTH, y0, GRID_CELL_SIZE - CHIP_BODY_WIDTH, GRID_CELL_SIZE),
                Qt.AlignCenter, str(item.index + 1))

    def paintMark(self, p, x, y, item):
        p.setPen(QPen(MARK_COLOR, 2))
        p.setFont(CHIP_FONT)

        x0, y0 = _GridToScene(x - 0.5, y - 0.5)
        parent = item.parent
        if parent.type == game.VIEW_ITEM_INPUT:
            x0 += MARK_OFFSET
        else:
            x0 -= MARK_OFFSET

        p.drawText(QRectF(x0, y0, GRID_CELL_SIZE, GRID_CELL_SIZE), Qt.AlignCenter, str(parent.index + 1))

    def boundingRect(self):
        sizeX, sizeY = self.size
        return QRectF(0, 0, sizeX * GRID_CELL_SIZE, sizeY * GRID_CELL_SIZE)

class Renderer(QObject):
    def __init__(self):
        super(Renderer, self).__init__()
        self.scene = QGraphicsScene()

        self.cellHighlight = QGraphicsRectItem(None, self.scene)
        self.cellHighlight.setPen(GRID_CELL_HIGHLIGHT_PEN)
        self.cellHighlight.setRect(0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE)
        self.cellHighlight.setVisible(False)
        self.cellHighlight.setPen(QPen(CHIP_COLOR))
        self.cellHighlight.setZValue(10) # top most

        self.grids = []

    def pushModel(self, model, offsetX = 0, offsetY = 0, showBorders = False):
        grid = RendGrid(model)
        x, y = offsetX * GRID_CELL_SIZE, offsetY * GRID_CELL_SIZE

        grid.setPos(x, y)

        if not showBorders:
            item = grid
        else:
            grid.border = QGraphicsRectItem(
                x - BORDER_WIDTH,
                y - BORDER_WIDTH,
                model.sizeX * GRID_CELL_SIZE + BORDER_WIDTH * 2,
                model.sizeY * GRID_CELL_SIZE + BORDER_WIDTH * 2)
            
            grid.border.setPen(QPen(BORDER_COLOR, 1, Qt.DashLine))
            grid.border.setBrush(QBrush(BORDER_BACK_COLOR))
            grid.setParentItem(grid.border)
            grid.border.setZValue(len(self.grids))
            item = grid.border

        grid.setZValue(len(self.grids))

        self.scene.addItem(item)
        self.grids.append(grid)

        self.cellHighlight.setVisible(False)

        if len(self.grids) == 1:
            self.connect(model, SIGNAL("ModelChanged()"), self.scene.invalidate)

        self.scene.invalidate()
        
        #self.emit(SIGNAL("GridResized()"))

    def popModel(self):
        grid = self.grids[-1]
        if hasattr(grid, "border"):
            self.scene.removeItem(grid.border)
        else:
            self.scene.removeItem(grid)
        self.grids.pop()

        if len(self.grids) == 0:
            self.disconnect(grid.model, SIGNAL("ModelChanged()"), self.scene.invalidate)

        self.scene.invalidate()

        #self.emit(SIGNAL("GridResized()"))
        
    def mapToGrid(self, pos):
        if len(self.grids) == 0:
            return None

        # pos : QPointF, position in scene
        grid = self.grids[-1]
        gridRect = grid.boundingRect()

        x, y = pos.x(), pos.y()
        left    = grid.pos().x()
        right   = left + gridRect.right()
        top     = grid.pos().y()
        bottom  = top + gridRect.bottom()

        if x < left or x >= right:
            return None
        if y < top  or y >= bottom:
            return None

        x = int(x - left + 0.5) // GRID_CELL_SIZE
        y = int(y - top  + 0.5) // GRID_CELL_SIZE
        return x, y

    def setHighlight(self, x, y):
        x, y = x * GRID_CELL_SIZE, y * GRID_CELL_SIZE
        gridPos = self.grids[-1].pos()
        x += gridPos.x()
        y += gridPos.y()
        self.cellHighlight.setPos(x, y)
        self.cellHighlight.setVisible(True)

    def setTitle(self, title):
        # set title to top grid
        if len(self.grids) > 0:
            self.grids[0].title = title
            self.scene.invalidate()

    def resetHighlight(self):
        self.cellHighlight.setVisible(False)
       